/**
 *  CloudEdit- Editor colaborativo peer-to-peer
 *  Author: Miguel Souto Bartolomé
 *  Web: http://code.google.com/p/cloudedit/
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.google.gwt.cloudedit.server;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.google.gwt.cloudedit.client.LlamadaPastry;
import com.google.gwt.cloudedit.server.diff_match_patch.Diff;
import com.google.gwt.cloudedit.server.diff_match_patch.Operation;
import com.google.gwt.cloudedit.server.exceptions.ExcepcionArchivo;
import com.google.gwt.cloudedit.shared.DatosArchivo;
import com.google.gwt.cloudedit.shared.DatosListado;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import easypastry.core.PastryKernel;
import easypastry.util.Utilities;

/**
 * La clase LlamadaPastryImpl implementa en el servidor la interfaz
 * LlamadaPastry del servicio RCP. Crea una nueva red Pastry de tabla Hash
 * distribuida, la asocia con la aplicación cliente del RCP y nos da la
 * funcionalidad asociada a dicha red.
 * 
 * @author Miguel Souto Bartolomé
 * @version 4.0
 */

public class LlamadaPastryImpl extends RemoteServiceServlet implements
		LlamadaPastry, ServletContextListener {
	

	/**
	 * 
	 */
	private static final long serialVersionUID = 2531059729922322188L;

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.cloudedit.client.LlamadaPastry#inicia()
	 */
	public synchronized void inicia() throws IllegalArgumentException {
		if ((Integer) getServletContext().getAttribute("iniciado") == 0) {
			try {
				PastryKernel.init(getServletContext().getRealPath("/")
						+ "easypastry-config.xml");
				System.out.println("Pastry inicia");
				getServletContext().setAttribute("archivero",
						new GestionArchivos("p2p://archivos"));
				System.out.println("Bunshin inicia");
				getServletContext().setAttribute("mensajero",
						new GestionMensajes("p2p://cast", getServletContext()));
				System.out.println("Cast inicia");
				PastryKernel.getPastryConnection().bootNode();
				System.out.println("Nodo arranca");
				ArrayList<DatosCliente> listeners = new ArrayList<DatosCliente>();
				getServletContext().setAttribute("listeners", listeners);

			} catch (Exception ex) {
				ex.printStackTrace();
			}
			try {
				((GestionArchivos) getServletContext()
						.getAttribute("archivero")).getList();
			} catch (ExcepcionArchivo ex) {
				try {
					((GestionArchivos) getServletContext().getAttribute(
							"archivero"))
							.setList(new ArrayList<DatosListado>());
					System.out.println("Creado listado nuevo");
				} catch (ExcepcionArchivo e) {
					e.printStackTrace();
				}
			}
			getServletContext().setAttribute("iniciado", 1);
		}

		System.out.println("Id sesion: "
				+ this.getThreadLocalRequest().getSession().getId());

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.google.gwt.cloudedit.client.LlamadaPastry#graba(com.google.gwt.cloudedit
	 * .shared.DatosArchivo)
	 */
	public void graba(DatosArchivo datos) throws IllegalArgumentException {
		DatosArchivo archivo = datos;
		archivo.setModified(new Date());
		archivo.setIp(this.getThreadLocalRequest().getRemoteAddr() + ":"
				+ this.getThreadLocalRequest().getRemotePort());
		try {
			((GestionArchivos) getServletContext().getAttribute("archivero"))
					.setFile(archivo);
			System.out.println(this.getThreadLocalRequest().getSession()
					.getId()
					+ " graba " + archivo.getName());
		} catch (ExcepcionArchivo e) {
			System.out.println(this.getThreadLocalRequest().getSession()
					.getId()
					+ "falla al grabar " + archivo.getName());
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.google.gwt.cloudedit.client.LlamadaPastry#grabaComo(com.google.gwt
	 * .cloudedit.shared.DatosArchivo)
	 */

	@Override
	public DatosArchivo grabaComo(DatosArchivo datos, String nombreAnterior)
			throws IllegalArgumentException {

		// Genera los datos faltantes necesarios del nuevo archivo y llama al
		// servicio graba()
		DatosArchivo archivo = datos;
		archivo = datos;
		archivo.setId(Utilities.generateStringHash(datos.getName()));
		archivo.setCreated(new Date());
		archivo.setModified(archivo.getCreated());
		graba(archivo);

		// Graba los datos del archivo en el listado
		try {
			ArrayList<DatosListado> listado = new ArrayList<DatosListado>();
			listado = ((GestionArchivos) getServletContext().getAttribute(
					"archivero")).getList();
			;
			synchronized (listado) {
				Iterator<DatosListado> itr = listado.iterator();
				while (itr.hasNext()) {
					DatosListado elemento = itr.next();

					// Si el archivo ya existía en el listado lo borra y vuelve
					// a escribir

					if (elemento.getId().equals(archivo.getId())) {
						listado.remove(elemento);
						break;
					}
				}
				listado
						.add((new DatosListado(archivo.getId(), archivo
								.getName(), archivo.getCreated(), archivo
								.getSizeXML())));
				((GestionArchivos) getServletContext()
						.getAttribute("archivero")).setList(listado);
			}
		} catch (ExcepcionArchivo e) {
			e.printStackTrace();
		}

		// Cambiar el MensajesListener asociado a la sesion
		cambioArchivo(archivo.getId(), Utilities
				.generateStringHash(nombreAnterior));

		return archivo;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.cloudedit.client.LlamadaPastry#abre(java.lang.String)
	 */
	@Override
	public DatosArchivo abre(String id, String nombreAnterior)
			throws IllegalArgumentException {
		try {
			DatosArchivo archivo = ((GestionArchivos) getServletContext()
					.getAttribute("archivero")).getFile(id);
			System.out.println(this.getThreadLocalRequest().getSession()
					.getId()
					+ " abre " + archivo.getName());
			if (!archivo.equals(nombreAnterior)) {
				cambioArchivo(archivo.getId(), Utilities
						.generateStringHash(nombreAnterior));
			}
			return archivo;

		} catch (ExcepcionArchivo e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.google.gwt.cloudedit.client.LlamadaPastry#notifica(java.lang.String)
	 */
	@Override
	public void notifica(String xml, DatosArchivo file)
			throws IllegalArgumentException {
		((GestionMensajes) getServletContext().getAttribute("mensajero"))
				.enviaMensaje(xml, file, this.getThreadLocalRequest());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.cloudedit.client.LlamadaPastry#comprueba()
	 */
	@Override
	public String comprueba() throws IllegalArgumentException {
		String mensaje;
		try {
			mensaje = ((GestionMensajes) getServletContext().getAttribute(
					"mensajero")).recibeMensaje(this.getThreadLocalRequest()
					.getSession());
			System.out.println(this.getThreadLocalRequest().getSession()
					.getId()
					+ " online recibe " + mensaje);
			return mensaje;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.cloudedit.client.LlamadaPastry#listado()
	 */
	@Override
	public ArrayList<DatosListado> listado() {
		try {
			ArrayList<DatosListado> listado = ((GestionArchivos) getServletContext()
					.getAttribute("archivero")).getList();
			System.out.println("Abriendo listado");
			return listado;
		} catch (Exception e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.google.gwt.cloudedit.client.LlamadaPastry#comprobarNombre(java.lang
	 * .String)
	 */
	@Override
	public String comprobarNombre(String text) throws IllegalArgumentException {
		if ((text == null) || (text.equals("Nuevo documento de texto")) || (text.equals(""))
				|| (text.equals("listado")))
			return "EXISTE";
		try {
			ArrayList<DatosListado> listado = ((GestionArchivos) getServletContext()
					.getAttribute("archivero")).getList();
			synchronized (listado) {
				Iterator<DatosListado> itr = listado.iterator();
				String nombre = Utilities.generateStringHash(text);
				while (itr.hasNext()) {
					DatosListado elemento = itr.next();
					if (elemento.getId().equals(nombre)) {
						return "EXISTE";
					}
				}
			}
			return text;
		} catch (Exception e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	@Override
	public String comparar(String textoAntiguo, String textoNuevo) {
		diff_match_patch comparador = new diff_match_patch();
		LinkedList<Diff> diffs = comparador.diff_main(textoAntiguo, textoNuevo,
				true);
		StringBuilder text = new StringBuilder();
		for (Diff aDiff : diffs) {
			if (aDiff.operation == Operation.INSERT) {
				if ((aDiff.text.charAt(aDiff.text.length() - 1) == '>')
						&& (aDiff.text.charAt(0) == '<')) {
					text.append(aDiff.text);
				} else {
					text.append("<span style=\"background-color: #ADFF2F;\">");
					text.append(aDiff.text);
					text.append("</span>");
				}
			} else if (aDiff.operation == Operation.EQUAL) {
				text.append(aDiff.text);
			}
		}
		return text.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.google.gwt.cloudedit.client.LlamadaPastry#cerrar(java.lang.String)
	 */
	@Override
	public void cerrar(DatosArchivo datos, String modoEdicion)
			throws IllegalArgumentException {
		if (!datos.getName().equals("Nuevo documento de texto")) {
			if (modoEdicion.equals("online")) {
				graba(datos);
				notifica("<accion>actualizar</accion>", datos);
			}
			cambioArchivo(Utilities
					.generateStringHash("Nuevo documento de texto"), datos
					.getId());
		}
		System.out.println("Cerrando sesion "
				+ this.getThreadLocalRequest().getSession().getId());
	}

	public void resetContexto(String operacion) {
		if (operacion.equals("Start")) {
			getServletContext().setAttribute("iniciado", 0);
			System.out.println("Inicia contexto");
		} else if (operacion.equals("End")) {
			PastryKernel.getPastryConnection().close();
			System.out.println("Cerrando nodo");
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seejavax.servlet.ServletContextListener#contextDestroyed(javax.servlet.
	 * ServletContextEvent)
	 */
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		PastryKernel.getPastryConnection().close();
		System.out.println("Cerrando nodo");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.servlet.ServletContextListener#contextInitialized(javax.servlet
	 * .ServletContextEvent)
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		event.getServletContext().setAttribute("iniciado", 0);
	}

	/**
	 * Cambia la correspondencia de la sesión de cliente actual con la edición
	 * de un archivo diferente. Los cambios se guardan en el atributo
	 * "listeners" de contexto del Servlet.
	 * 
	 * @param idNuevo identificador del nuevo archivo a editar
	 * @param idAnterior identificador del archivo que se estaba editado
	 */
	@SuppressWarnings("unchecked")
	private synchronized void cambioArchivo(String idNuevo, String idAnterior) {
		if (!idAnterior.equals(Utilities
				.generateStringHash("Nuevo documento de texto"))) {
			Iterator<DatosCliente> itr = ((ArrayList<DatosCliente>) getServletContext()
					.getAttribute("listeners")).iterator();
			while (itr.hasNext()) {
				DatosCliente elemento = itr.next();

				if (elemento.getSesion().getId().equals(
						this.getThreadLocalRequest().getSession().getId())) {
					((ArrayList<DatosCliente>) getServletContext()
							.getAttribute("listeners")).remove(elemento);
					break;
				}
			}
		}
		if (!idNuevo.equals(Utilities
				.generateStringHash("Nuevo documento de texto"))) {
			((ArrayList<DatosCliente>) getServletContext().getAttribute(
					"listeners")).add(new DatosCliente(this
					.getThreadLocalRequest().getSession(), idNuevo));
		}
	}

}